/**
 * MojoSetup; a portable, flexible installation application.
 *
 * Please see the file LICENSE.txt in the source's root directory.
 *
 *  This file written by Ryan C. Gordon.
 *
      Copyright (c) 2006-2010 Ryan C. Gordon and others.

   This software is provided 'as-is', without any express or implied warranty.
   In no event will the authors be held liable for any damages arising from
   the use of this software.

   Permission is granted to anyone to use this software for any purpose,
   including commercial applications, and to alter it and redistribute it
   freely, subject to the following restrictions:

   1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software in a
   product, an acknowledgment in the product documentation would be
   appreciated but is not required.

   2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.

   3. This notice may not be removed or altered from any source distribution.

       Ryan C. Gordon <icculus@icculus.org>

 */

#if !SUPPORT_GUI_STDIO
#error Something is wrong in the build system.
#endif

#define BUILDING_EXTERNAL_PLUGIN 1
#include "gui.h"

MOJOGUI_PLUGIN(stdio)

#if !GUI_STATIC_LINK_STDIO
CREATE_MOJOGUI_ENTRY_POINT(stdio)
#endif

#include <ctype.h>

static char *lastProgressType = NULL;
static char *lastComponent = NULL;
static uint32 percentTicks = 0;

static int read_stdin(char *buf, int len)
{
    if (fgets(buf, len, stdin) == NULL)
        return -1;

    len = strlen(buf) - 1;
    while ( (len >= 0) && ((buf[len] == '\n') || (buf[len] == '\r')) )
        buf[len--] = '\0';

    return len+1;
} // read_stdin


static int readstr(const char *prompt, char *buf, int len,
                   boolean back, boolean fwd)
{
    // !!! FIXME: if read_stdin() returns -1, we return 0, which makes it
    // !!! FIXME:  indistinguishable from "user hit enter" ... maybe we should
    // !!! FIXME:  abort in read_stdin() if i/o fails?

    int retval = 0;
    char *backstr = (back) ? xstrdup(_("back")) : NULL;

    if (prompt != NULL)
        printf("%s\n", prompt);

    if (back)
    {
        char *fmt = xstrdup(_("Type '%0' to go back."));
        char *msg = format(fmt, backstr);
        printf("%s\n", msg);
        free(msg);
        free(fmt);
    } // if

    if (fwd)
    {
        printf("%s", _("Press enter to continue."));
        printf("\n");
    } // if

    printf("%s",_("> "));
    fflush(stdout);

    if ((retval = read_stdin(buf, len)) >= 0)
    {
        if ((back) && (strcmp(buf, backstr) == 0))  // !!! FIXME: utf8casecmp?
            retval = -1;
    } // if

    free(backstr);
    return retval;
} // readstr


static uint8 MojoGui_stdio_priority(boolean istty)
{
    // if not a tty and no other GUI plugins worked out, let the base
    //  application try to spawn a terminal and try again. If it can't do so,
    //  it will panic() and thus end the process, so we don't end up blocking
    //  on some prompt the user can't see.

    if (!istty)
        return MOJOGUI_PRIORITY_NEVER_TRY;

    return MOJOGUI_PRIORITY_TRY_ABSOLUTELY_LAST;  // always a last resort.
} // MojoGui_stdio_priority


static boolean MojoGui_stdio_init(void)
{
    percentTicks = 0;
    return true;  // always succeeds.
} // MojoGui_stdio_init


static void MojoGui_stdio_deinit(void)
{
    free(lastProgressType);
    free(lastComponent);
    lastProgressType = NULL;
    lastComponent = NULL;
} // MojoGui_stdio_deinit


static void MojoGui_stdio_msgbox(const char *title, const char *text)
{
    char buf[128];
    char *fmt = xstrdup(_("NOTICE: %0\n[hit enter]"));
    char *msg = format(fmt, text);
    printf("%s\n", msg);
    free(msg);
    free(fmt);
    fflush(stdout);
    read_stdin(buf, sizeof (buf));
} // MojoGui_stdio_msgbox


static boolean MojoGui_stdio_promptyn(const char *title, const char *text,
                                      boolean defval)
{
    boolean retval = false;
    if (!feof(stdin))
    {
        const char *_fmt = ((defval) ? _("%0 [Y/n]: ") : _("%0 [y/N]: "));
        char *fmt = xstrdup(_fmt);
        char *msg = format(fmt, text);
        char *localized_no = xstrdup(_("N"));
        char *localized_yes = xstrdup(_("Y"));
        boolean getout = false;
        char buf[128];

        while (!getout)
        {
            int rc = 0;

            getout = true;  // we may reset this later.
            printf("%s", msg);
            fflush(stdout);
            rc = read_stdin(buf, sizeof (buf));

            if (rc < 0)
                retval = false;
            else if (rc == 0)
                retval = defval;
            else if (strcasecmp(buf, localized_no) == 0)
                retval = false;
            else if (strcasecmp(buf, localized_yes) == 0)
                retval = true;
            else
                getout = false;  // try again.
        } // while

        free(localized_yes);
        free(localized_no);
        free(msg);
        free(fmt);
    } // if

    return retval;
} // MojoGui_stdio_promptyn


static MojoGuiYNAN MojoGui_stdio_promptynan(const char *title, const char *txt,
                                            boolean defval)
{
    MojoGuiYNAN retval = MOJOGUI_NO;
    if (!feof(stdin))
    {
        char *fmt = xstrdup(_("%0\n[y/n/Always/Never]: "));
        char *msg = format(fmt, txt);
        char *localized_no = xstrdup(_("N"));
        char *localized_yes = xstrdup(_("Y"));
        char *localized_always = xstrdup(_("Always"));
        char *localized_never = xstrdup(_("Never"));
        boolean getout = false;
        char buf[128];

        while (!getout)
        {
            int rc = 0;

            getout = true;  // we may reset this later.
            printf("%s\n", msg);
            fflush(stdout);
            rc = read_stdin(buf, sizeof (buf));

            if (rc < 0)
                retval = MOJOGUI_NO;
            else if (rc == 0)
                retval = (defval) ? MOJOGUI_YES : MOJOGUI_NO;
            else if (strcasecmp(buf, localized_no) == 0)
                retval = MOJOGUI_NO;
            else if (strcasecmp(buf, localized_yes) == 0)
                retval = MOJOGUI_YES;
            else if (strcasecmp(buf, localized_always) == 0)
                retval = MOJOGUI_ALWAYS;
            else if (strcasecmp(buf, localized_never) == 0)
                retval = MOJOGUI_NEVER;
            else
                getout = false;  // try again.
        } // while

        free(localized_never);
        free(localized_always);
        free(localized_yes);
        free(localized_no);
        free(msg);
        free(fmt);
    } // if

    return retval;
} // MojoGui_stdio_promptynan


static boolean MojoGui_stdio_start(const char *title,
                                   const MojoGuiSplash *splash)
{
    printf("%s\n", title);
    return true;
} // MojoGui_stdio_start


static void MojoGui_stdio_stop(void)
{
    // no-op.
} // MojoGui_stdio_stop


static void dumb_pager(const char *name, const char *data, size_t datalen)
{
    const int MAX_PAGE_LINES = 21;
    char *fmt = xstrdup(_("(%0-%1 of %2 lines, see more?)"));
    int i = 0;
    int w = 0;
    int linecount = 0;
    boolean getout = false;
    char **lines = splitText(data, 80, &linecount, &w);

    assert(linecount >= 0);

    printf("%s\n", name);

    if (lines == NULL)  // failed to parse?!
        printf("%s\n", data);  // just dump it all. Oh well.
    else
    {
        int printed = 0;
        do
        {
            for (i = 0; (i < MAX_PAGE_LINES) && (printed < linecount); i++)
                printf("%s", lines[printed++]);

            if (printed >= linecount)
                getout = true;
            else
            {
                char *msg = NULL;
                printf("\n");
                msg = format(fmt, numstr((printed-i)+1),
                             numstr(printed), numstr(linecount));
                getout = !MojoGui_stdio_promptyn("", msg, true);
                free(msg);
                printf("\n");
            } // else
        } while (!getout);

        for (i = 0; i < linecount; i++)
            free(lines[i]);
        free(lines);
    } // while

    free(fmt);
} // dumb_pager


static int MojoGui_stdio_readme(const char *name, const uint8 *_data,
                                size_t datalen, boolean can_back,
                                boolean can_fwd)
{
    const char *data = (const char *) _data;
    char buf[256];
    int retval = -1;
    boolean failed = true;

    // !!! FIXME: popen() isn't reliable.
    #if 0  //PLATFORM_UNIX
    const size_t namelen = strlen(name);
    const char *programs[] = { getenv("PAGER"), "more", "less -M", "less" };
    int i = 0;

    // flush streams, so output doesn't mingle with the popen()'d process.
    fflush(stdout);
    fflush(stderr);

    for (i = 0; i < STATICARRAYLEN(programs); i++)
    {
        const char *cmd = programs[i];
        if (cmd != NULL)
        {
            FILE *io = popen(cmd, "w");
            if (io != NULL)
            {
                failed = false;
                if (!failed) failed = (fwrite("\n", 1, 1, io) != 1);
                if (!failed) failed = (fwrite(name, namelen, 1, io) != 1);
                if (!failed) failed = (fwrite("\n", 1, 1, io) != 1);
                if (!failed) failed = (fwrite(data, datalen, 1, io) != 1);
                if (!failed) failed = (fwrite("\n", 1, 1, io) != 1);
                failed |= (pclose(io) != 0);  // call whether we failed or not.
                if (!failed)
                    break;  // it worked, we're done!
            } // if
        } // if
    } // for
    #endif // PLATFORM_UNIX

    if (failed)  // We're not Unix, or none of the pagers worked?
        dumb_pager(name, data, datalen);

    // Put up the "hit enter to continue (or 'back' to go back)" prompt,
    //  but only if there's an choice to be made here.
    if ((!can_back) || (readstr(NULL, buf, sizeof (buf), can_back, true) >= 0))
        retval = 1;

    return retval;
} // MojoGui_stdio_readme


static void toggle_option(MojoGuiSetupOptions *parent,
                          MojoGuiSetupOptions *opts, int *line, int target)
{
    if ((opts != NULL) && (target > *line))
    {
        if (!opts->is_group_parent)
        {
            if (++(*line) == target)
            {
                const boolean toggled = ((opts->value) ? false : true);

                // "radio buttons" in a group?
                if ((parent) && (parent->is_group_parent))
                {
                    if (toggled)  // drop unless we weren't the current toggle.
                    {
                        // set all siblings to false...
                        MojoGuiSetupOptions *i = parent->child;
                        while (i != NULL)
                        {
                            i->value = false;
                            i = i->next_sibling;
                        } // while
                        opts->value = true;  // reset us to be true.
                    } // if
                } // if

                else  // individual "check box" was chosen.
                {
                    opts->value = toggled;
                } // else

                return;  // we found it, bail.
            } // if
        } // if

        if (opts->value) // if option is toggled on, descend to children.
            toggle_option(opts, opts->child, line, target);
        toggle_option(parent, opts->next_sibling, line, target);
    } // if
} // toggle_option


static void print_options(MojoGuiSetupOptions *opts, int *line, int level)
{
    if (opts != NULL)
    {
        int i;
        int spacing = 1;
        if (opts->is_group_parent)
            spacing += 6;
        else
        {
            (*line)++;
            printf("%2d  [%c]", *line, opts->value ? 'X' : ' ');
        } // else

        for (i = 0; i < (level + spacing); i++)
            putchar(' ');

        printf("%s%s\n", opts->description, opts->is_group_parent ? ":" : "");

        if ((opts->value) || (opts->is_group_parent))
            print_options(opts->child, line, level+1);
        print_options(opts->next_sibling, line, level);
    } // if
} // print_options


static int MojoGui_stdio_options(MojoGuiSetupOptions *opts,
                                 boolean can_back, boolean can_fwd)
{
    const char *inst_opts_str = xstrdup(_("Options"));
    const char *prompt = xstrdup(_("Choose number to change."));
    int retval = -1;
    boolean getout = false;
    char buf[128];
    int len = 0;

    while (!getout)
    {
        int line = 0;

        printf("\n\n");
        printf("%s", inst_opts_str);
        printf("\n");
        print_options(opts, &line, 1);
        printf("\n");

        if ((len = readstr(prompt, buf, sizeof (buf), can_back, true)) < 0)
            getout = true;
        else if (len == 0)
        {
            getout = true;
            retval = 1;
        } // else if
        else
        {
            char *endptr = NULL;
            int target = (int) strtol(buf, &endptr, 10);
            if (*endptr == '\0')  // complete string was a valid number?
            {
                line = 0;
                toggle_option(NULL, opts, &line, target);
            } // if
        } // else
    } // while

    free((void *) inst_opts_str);
    free((void *) prompt);

    return retval;
} // MojoGui_stdio_options


static char *MojoGui_stdio_destination(const char **recommends, int recnum,
                                       int *command, boolean can_back,
                                       boolean can_fwd)
{
    const char *instdeststr = xstrdup(_("Destination"));
    const char *prompt = NULL;
    char *retval = NULL;
    boolean getout = false;
    char buf[128];
    int len = 0;
    int i = 0;

    *command = -1;

    if (recnum > 0)
        prompt = xstrdup(_("Choose install destination by number (hit enter for #1), or enter your own."));
    else
        prompt = xstrdup(_("Enter path where files will be installed."));

    while (!getout)
    {
        printf("\n\n%s\n", instdeststr);
        for (i = 0; i < recnum; i++)
            printf("  %2d  %s\n", i+1, recommends[i]);
        printf("\n");

        if ((len = readstr(prompt, buf, sizeof (buf), can_back, false)) < 0)
            getout = true;

        else if ((len == 0) && (recnum > 0))   // default to first in list.
        {
            retval = xstrdup(recommends[0]);
            *command = 1;
            getout = true;
        } // else if

        else if (len > 0)
        {
            char *endptr = NULL;
            int target = (int) strtol(buf, &endptr, 10);
            // complete string was a valid number?
            if ((*endptr == '\0') && (target > 0) && (target <= recnum))
                retval = xstrdup(recommends[target-1]);
            else
                retval = xstrdup(buf);

            *command = 1;
            getout = true;
        } // else
    } // while

    free((void *) prompt);
    free((void *) instdeststr);

    return retval;
} // MojoGui_stdio_destination


static int MojoGui_stdio_productkey(const char *desc, const char *fmt,
                                    char *buf, const int buflen,
                                    boolean can_back, boolean can_fwd)
{
    const char *prompt = xstrdup(_("Please enter your product key"));
    char *defval = ((*buf) ? xstrdup(buf) : NULL);
    boolean getout = false;
    int retval = -1;
    char *msg = NULL;

    if (defval != NULL)
    {
        char *locfmt = xstrdup(_("(just press enter to use '%0')"));
        msg = format(locfmt, defval);
        free(locfmt);
    } // if

    while (!getout)
    {
        int len;
        printf("\n\n%s\n", desc);
        if (msg != NULL)
            printf("%s\n", msg);
        if ((len = readstr(prompt, buf, buflen, can_back, false)) < 0)
            getout = true;
        else
        {
            if ((len == 0) && (defval != NULL))
                strcpy(buf, defval);

            if (isValidProductKey(fmt, buf))
            {
                retval = 1;
                getout = true;
            } // else if
            else
            {
                // We can't check the input character-by-character, so reuse
                //  the failed-verification localized string.
                printf("\n%s\n\n",
                        _("That key appears to be invalid. Please try again."));
            } // else
        } // else
    } // while

    free(msg);
    free(defval);
    free((void *) prompt);

    return retval;
} // MojoGui_stdio_productkey


static boolean MojoGui_stdio_insertmedia(const char *medianame)
{
    char buf[32];
    char *fmt = xstrdup(_("Please insert '%0'"));
    char *msg = format(fmt, medianame);
    printf("%s\n", _("Media change"));
    printf("%s\n", msg);
    free(msg);
    free(fmt);
    return (readstr(NULL, buf, sizeof (buf), false, true) >= 0);
} // MojoGui_stdio_insertmedia


static void MojoGui_stdio_progressitem(void)
{
    // force new line of output on next call to MojoGui_stdio_progress()
    percentTicks = 0;
} // MojoGui_stdio_progressitem


static boolean MojoGui_stdio_progress(const char *type, const char *component,
                                      int percent, const char *item,
                                      boolean can_cancel)
{
    const uint32 now = ticks();

    if ( (lastComponent == NULL) ||
         (strcmp(lastComponent, component) != 0) ||
         (lastProgressType == NULL) ||
         (strcmp(lastProgressType, type) != 0) )
    {
        free(lastProgressType);
        free(lastComponent);
        lastProgressType = xstrdup(type);
        lastComponent = xstrdup(component);
        printf("%s\n%s\n", type, component);
    } // if

    // limit update spam... will only write every one second, tops,
    //  on any given filename, but it writes each filename at least once
    //  so it doesn't look like we only installed a few things.
    if (percentTicks <= now)
    {
        char *fmt = NULL;
        char *msg = NULL;
        percentTicks = now + 1000;
        if (percent < 0)
            printf("%s\n", item);
        else
        {
            fmt = xstrdup(_("%0 (total progress: %1%%)"));
            msg = format(fmt, item, numstr(percent));
            printf("%s\n", msg);
            free(msg);
            free(fmt);
        } // else
    } // if

    return true;
} // MojoGui_stdio_progress


static void MojoGui_stdio_final(const char *msg)
{
    printf("%s\n\n", msg);
    fflush(stdout);
} // MojoGui_stdio_final

// end of gui_stdio.c ...

